﻿using System;
using System.Collections.Generic;

namespace IBNet.DayTrade
{
   /// <summary>
   /// A factory for creating instrument-specific (Stock, Future, Currency)
   /// and futures-contract-specific (ES, YM)
   /// profit limit, trailing stop, and time stop bracket orders.
   /// </summary>
   public static class BracketOrderFactory
   {
      private readonly static Dictionary<string, BracketOrderConstantsBag>
         fullSymbolToBracketOrderConstants;

      static readonly TimeSpan MaximumDayTradeDuration = new TimeSpan(0, 5, 0);

      // Stocks
      const decimal StockLongTargetPercentage = 1.05m;
      const decimal StockShortTargetPercentage = 0.95m;
      const decimal StockTrailingPercentage = 0.05m;
      // Stocks trades from 6:30 AM - 1:00 PM PST
      static readonly DateTime StockOrderExpirationTime 
         = DateTime.Today + new TimeSpan(12, 59, 0);

      // YM
      const int YMTargetPoints = 20;
      const int YMTrailingPoints = 20;
      // YM trades from 6:30 AM - 1:15 PM PST
      static readonly DateTime YMOrderExpirationTime 
         = DateTime.Today + new TimeSpan(13, 14, 0);

      // ES
      const int ESTargetPoints = 5;
      const int ESTrailingPoints = 2;
      // ES trades from 6:30 AM - 1:15 PM PST
      static readonly DateTime ESOrderExpirationTime 
         = DateTime.Today + new TimeSpan(13, 14, 0);

      static BracketOrderFactory()
      {
         fullSymbolToBracketOrderConstants =
            new Dictionary<string, BracketOrderConstantsBag>();

         BracketOrderConstantsBag ymBracketOrderConstants = new BracketOrderConstantsBag(
            YMTargetPoints, YMTrailingPoints, YMOrderExpirationTime);
         BracketOrderConstantsBag esBracketOrderConstants = new BracketOrderConstantsBag(
            ESTargetPoints, ESTrailingPoints, ESOrderExpirationTime);

         fullSymbolToBracketOrderConstants.Add(
            DayTradeMainForm.YMSymbol, ymBracketOrderConstants);
         fullSymbolToBracketOrderConstants.Add(
            DayTradeMainForm.ESSymbol, esBracketOrderConstants);
      }

      #region NewTargetOrder

      /// <summary>
      /// Creates a new target limit bracket order 
      /// from an opening order ExecutionDetailsEventArgs and a one-cancels-all group.
      /// </summary>
      public static Order NewTargetOrder(ExecutionDetailsEventArgs e,
         string oneCancelsAllGroup)
      {
         if (e == null)
            throw new ArgumentNullException("e");
         if (String.IsNullOrEmpty(oneCancelsAllGroup))
            throw new ArgumentException("oneCancelsAllGroup");

         Order targetOrder = new Order()
         {
            OrderType = OrderType.Limit,
            TimeInForce = TimeInForce.GoodTillCancel,
            TotalQuantity = e.Execution.Shares,
            OneCancelsAllGroup = oneCancelsAllGroup,
            Flavor = OrderFlavor.Bracket
         };
         targetOrder.ActionSide = GetActionSideFromOpeningOrderExecutionSide(
            e.Execution.Side);

         SecurityType openingOrderSecurityType = e.Contract.SecurityType;
         decimal openingOrderFillPrice = e.Execution.FillPrice;

         switch (openingOrderSecurityType)
         {
         case SecurityType.Stock:
         {
            if (targetOrder.ActionSide == ActionSide.Sell)
            {
               targetOrder.LimitPrice = Math.Round(
                  StockLongTargetPercentage * openingOrderFillPrice, 2);
            }
            else
            {
               targetOrder.LimitPrice = Math.Round(
                  StockShortTargetPercentage * openingOrderFillPrice, 2);
            }
            break;
         }
         case SecurityType.Future:
         {
            BracketOrderConstantsBag futureConstants =
               fullSymbolToBracketOrderConstants[e.Contract.FullSymbol];

            if (targetOrder.ActionSide == ActionSide.Sell)
            {
               targetOrder.LimitPrice = openingOrderFillPrice
                  + futureConstants.TargetAmount;
            }
            else
            {
               targetOrder.LimitPrice = openingOrderFillPrice
                  - futureConstants.TargetAmount;
            }
            break;
         }
         case SecurityType.Cash:
         {
            throw new NotImplementedException();
         }
         default:
         {
            throw new ArgumentException("Invalid SecurityType. SecurityType must be Stock, Future, or Cash.");
         }
         }
         return targetOrder;
      }

      #endregion NewTargetOrder

      #region NewTrailingStopOrder

      /// <summary>
      /// Creates a new trailing stop bracket order
      /// from an opening order ExecutionDetailsEventArgs and a one-cancels-all group.
      /// </summary>
      public static Order NewTrailingStopOrder(ExecutionDetailsEventArgs e,
         string oneCancelsAllGroup)
      {
         if (e == null)
            throw new ArgumentNullException("e");
         if (String.IsNullOrEmpty(oneCancelsAllGroup))
            throw new ArgumentException("oneCancelsAllGroup");

         Order trailingStopOrder = new Order()
         {
            OrderType = OrderType.TrailingStop,
            TimeInForce = TimeInForce.GoodTillCancel,
            TotalQuantity = e.Execution.Shares,
            OneCancelsAllGroup = oneCancelsAllGroup,
            Flavor = OrderFlavor.Bracket
         };
         trailingStopOrder.ActionSide =
            GetActionSideFromOpeningOrderExecutionSide(e.Execution.Side);

         SecurityType openingOrderSecurityType = e.Contract.SecurityType;
         decimal openingOrderFillPrice = e.Execution.FillPrice;

         switch (openingOrderSecurityType)
         {
         case SecurityType.Stock:
         {
            trailingStopOrder.AuxPrice = Math.Round(
                  StockTrailingPercentage * openingOrderFillPrice, 2);
            break;
         }
         case SecurityType.Future:
         {
            BracketOrderConstantsBag futureConstants =
               fullSymbolToBracketOrderConstants[e.Contract.FullSymbol];
            trailingStopOrder.AuxPrice = futureConstants.TrailingAmount;
            break;
         }
         case SecurityType.Cash:
         {
            throw new NotImplementedException();
         }
         default:
         {
            throw new ArgumentException("Invalid SecurityType. SecurityType must be Stock, Future, or Cash.");
         }
         }
         return trailingStopOrder;
      }

      #endregion // NewTrailingStopOrder

      #region NewTimeStopOrder

      /// <summary>
      /// Creates a new time stop bracket order
      /// from an opening order ExecutionDetailsEventArgs and a one-cancels-all group.
      /// </summary>
      public static Order NewTimeStopOrder(ExecutionDetailsEventArgs e,
         string oneCancelsAllGroup)
      {
         if (e == null)
            throw new ArgumentNullException("e");
         if (String.IsNullOrEmpty(oneCancelsAllGroup))
            throw new ArgumentException("oneCancelsAllGroup");

         Order timeStopOrder = new Order()
         {
            OrderType = OrderType.Market,
            TimeInForce = TimeInForce.GoodTillCancel,
            TotalQuantity = e.Execution.Shares,
            OneCancelsAllGroup = oneCancelsAllGroup,
            Flavor = OrderFlavor.Bracket
         };
         timeStopOrder.ActionSide = GetActionSideFromOpeningOrderExecutionSide(
            e.Execution.Side);

         SecurityType openingOrderSecurityType = e.Contract.SecurityType;
         decimal openingOrderFillPrice = e.Execution.FillPrice;

         switch (openingOrderSecurityType)
         {
         case SecurityType.Stock:
         {
            timeStopOrder.GoodAfterTime = GetGoodAfterTime(DateTime.Now,
               MaximumDayTradeDuration, StockOrderExpirationTime);
            break;
         }
         case SecurityType.Future:
         {
            BracketOrderConstantsBag bracketOrderConstants =
               fullSymbolToBracketOrderConstants[e.Contract.FullSymbol];

            timeStopOrder.GoodAfterTime = GetGoodAfterTime(DateTime.Now,
               MaximumDayTradeDuration, bracketOrderConstants.GoodAfterTime);
            break;
         }
         case SecurityType.Cash:
         {
            throw new NotImplementedException();
         }
         default:
         {
            throw new ArgumentException("Invalid SecurityType. SecurityType must be Stock, Future, or Cash..");
         }
         }
         return timeStopOrder;
      }

      /// <summary>
      /// Gets a goodAfterTime string that is a specified number of minutes and seconds 
      /// into the future. If the specified number of minutes into the future is past a specified
      /// nearMarketCloseTime, then the goodAfterTime will be the nearMarketCloseTime
      /// instead of now + specified number of minutes into the future.
      /// </summary>
      /// <param name="latestOrderExpirationTime">The latest you want a Contract to
      /// stay active. E.g. 12:59:30 PST for right before market close time.</param>
      /// <returns>An "yyyyMMdd HH:mm:ss" goodAfterTime string.</returns>
      public static string GetGoodAfterTime(DateTime now, 
         TimeSpan maxDayTradeDuration, DateTime latestOrderExpirationTime)
      {
         if (maxDayTradeDuration <= TimeSpan.Zero)
            throw new ArgumentOutOfRangeException("maxDayTradeDuration", "maxDayTradeDuration cannot be less than TimeSpan.Zero");

         DateTime nowPlusMaxDayTradeDuration = now + maxDayTradeDuration;
         string goodAfterTime;
         if (nowPlusMaxDayTradeDuration <= latestOrderExpirationTime)
         // Example: If nowPlusMaxDayTradeDuration = 12:45 < nearMarketCloseTime = 12:59
         // then goodAfterTime = 12:45.
         {
            goodAfterTime = nowPlusMaxDayTradeDuration.ToString("yyyyMMdd HH:mm:ss");
         }
         else
         // Example: If nowPlusMaxDayTradeDuration = 13:05 and nearMarketClose = 12:59
         // then goodAfterTime = 12:59.
         {
            goodAfterTime = latestOrderExpirationTime.ToString("yyyyMMdd HH:mm:ss");
         }
         return goodAfterTime;
      }

      /// <summary>
      /// Returns the opposite ActionSide to an opening order's ExecutionSide.
      /// </summary>
      private static ActionSide GetActionSideFromOpeningOrderExecutionSide(
         ExecutionSide openingOrderExecutionSide)
      {
         switch (openingOrderExecutionSide)
         {
         case ExecutionSide.Bought:
         {
            return ActionSide.Sell;
         }
         case ExecutionSide.Sold:
         {
            return ActionSide.Buy;
         }
         default:
         {
            throw new ArgumentException("Invalid ExecutionSide");
         }
         }
      }

      #endregion // NewTimeStopOrder
   }
}
